﻿using System;
using System.Collections.Generic;

using System.Text;
using RoughJs.Helper;

namespace RoughJs.Ast
{
    /// <summary>
    /// 2010-01-5 ----- 2010-01-29
    /// 陈曦 1.0
    /// Microsoft Public License (Ms-PL)
    ///This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
    ///1. Definitions
    ///The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
    ///A "contribution" is the original software, or any additions or changes to the software.
    ///A "contributor" is any person that distributes its contribution under this license.
    ///"Licensed patents" are a contributor's patent claims that read directly on its contribution.
    ///2. Grant of Rights
    ///(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
    ///(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
    ///3. Conditions and Limitations
    ///(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
    ///(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
    ///(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
    ///(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
    ///(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 
    /// </summary>
    public class Decompiler
    {

     
        public static int ONLY_BODY_FLAG = 1 << 0;

       
        public static int TO_SOURCE_FLAG = 1 << 1;

        public static int INITIAL_INDENT_PROP = 1;

        
        public static int INDENT_GAP_PROP = 2;

        
        public static int CASE_GAP_PROP = 3;

       
        private const int FUNCTION_END = Token.LAST_TOKEN + 1;

        /// <summary>
        /// 得到当前代码中的偏移量
        /// </summary>
        /// <returns></returns>
        public int getCurrentOffset()
        {
            return sourceTop;
        }

        /// <summary>
        /// 加入标记
        /// </summary>
        /// <param name="token"></param>
        public void addToken(int token)
        {
            if (!(0 <= token && token <= Token.LAST_TOKEN))
                throw new ArgumentException();

            append((char)token);
        }

        public String getEncodedSource()
        {
            return sourceToString(0);
        }
        private String sourceToString(int offset)
        {
            if (offset < 0 || sourceTop < offset) Kit.codeBug();
            return new String(sourceBuffer, offset, sourceTop - offset);
        }

        public int markFunctionEnd(int functionStart)
        {
            int offset = getCurrentOffset();
            append((char)FUNCTION_END);
            return offset;
        }

        private void append(char c)
        {
            if (sourceTop == sourceBuffer.Length)
            {
                increaseSourceCapacity(sourceTop + 1);
            }
            sourceBuffer[sourceTop] = c;
            ++sourceTop;
        }
        private void increaseSourceCapacity(int minimalCapacity)
        {
            // Call this only when capacity increase is must
            if (minimalCapacity <= sourceBuffer.Length) Kit.codeBug();
            int newCapacity = sourceBuffer.Length * 2;
            if (newCapacity < minimalCapacity)
            {
                newCapacity = minimalCapacity;
            }
            char[] tmp = new char[newCapacity];
            Array.Copy(sourceBuffer, 0, tmp, 0, sourceTop);
            //System.arraycopy(sourceBuffer, 0, tmp, 0, );
            sourceBuffer = tmp;
        }
        public void addNumber(double n)
        {
            addToken(Token.NUMBER);

            /* encode the number in the source stream.
             * Save as NUMBER type (char | char char char char)
             * where type is
             * 'D' - double, 'S' - short, 'J' - long.

             * We need to retain float vs. integer type info to keep the
             * behavior of liveconnect type-guessing the same after
             * decompilation.  (Liveconnect tries to present 1.0 to Java
             * as a float/double)
             * OPT: This is no longer true. We could compress the format.

             * This may not be the most space-efficient encoding;
             * the chars created below may take up to 3 bytes in
             * constant pool UTF-8 encoding, so a Double could take
             * up to 12 bytes.
             */

            long lbits = (long)n;
            if (lbits != n)
            {
                // if it's floating point, save as a Double bit pattern.
                // (12/15/97 our scanner only returns Double for f.p.)
                lbits = BitConverter.DoubleToInt64Bits(n);
                append('D');
                append((char)(lbits >> 48));
                append((char)(lbits >> 32));
                append((char)(lbits >> 16));
                append((char)lbits);
            }
            else
            {
                // we can ignore negative values, bc they're already prefixed
                // by NEG
                if (lbits < 0) Kit.codeBug();

                // will it fit in a char?
                // this gives a short encoding for integer values up to 2^16.
                if (lbits <= Char.MaxValue)
                {
                    append('S');
                    append((char)lbits);
                }
                else
                { // Integral, but won't fit in a char. Store as a long.
                    append('J');
                    append((char)(lbits >> 48));
                    append((char)(lbits >> 32));
                    append((char)(lbits >> 16));
                    append((char)lbits);
                }
            }
        }
        /// <summary>
        /// 加入标识符
        /// </summary>
        /// <param name="str"></param>
        public void addName(String str)
        {
            addToken(Token.NAME);
            appendString(str);
        }
        public void addString(String str)
        {
            addToken(Token.STRING);
            appendString(str);
        }

        public void addEOL(int token)
        {
            if (!(0 <= token && token <= Token.LAST_TOKEN))
                throw new ArgumentException();

            append((char)token);
            append((char)Token.EOL);
        }

        public int markFunctionStart(int functionType)
        {
            int savedOffset = getCurrentOffset();
            addToken(Token.FUNCTION);
            append((char)functionType);
            return savedOffset;
        }

        /// <summary>
        /// 标识名
        /// </summary>
        /// <param name="str"></param>
        private void appendString(String str)
        {
            int L = str.Length;
            int lengthEncodingSize = 1;
            if (L >= 0x8000)
            {
                lengthEncodingSize = 2;
            }
            int nextTop = sourceTop + lengthEncodingSize + L;
            if (nextTop > sourceBuffer.Length)
            {
                increaseSourceCapacity(nextTop);
            }
            if (L >= 0x8000)
            {
                // Use 2 chars to encode strings exceeding 32K, were the highest
                // bit in the first char indicates presence of the next byte
                sourceBuffer[sourceTop] = (char)(0x8000 | MathKit.foo(L, 16));
                ++sourceTop;
            }
            sourceBuffer[sourceTop] = (char)L;//写入长度
            ++sourceTop;
            char[] strArr = str.ToCharArray();
            for (int i = 0, j = sourceTop; i < L; i++)
            {
                sourceBuffer[sourceTop + i] = strArr[i];
            }
            // strArr.CopyTo(
            // Array.Copy(strArr,0,
            //  new String(
            //str.getChars(0, L, sourceBuffer, sourceTop);
            sourceTop = nextTop;
        }

        public void addRegexp(String regexp, String flags)
        {
            addToken(Token.REGEXP);
            appendString('/' + regexp + '/' + flags);
        }



        public static String decompile(String source, int flags,
                                   UintMap properties)
        {
            int length = source.Length;
            if (length == 0) { return ""; }

            int indent = properties.getInt(INITIAL_INDENT_PROP, 0);
            if (indent < 0) throw new ArgumentException();
            int indentGap = properties.getInt(INDENT_GAP_PROP, 4);
            if (indentGap < 0) throw new ArgumentException();
            int caseGap = properties.getInt(CASE_GAP_PROP, 2);
            if (caseGap < 0) throw new ArgumentException();

            StringBuilder result = new StringBuilder();
            bool justFunctionBody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
            bool toSource = (0 != (flags & Decompiler.TO_SOURCE_FLAG));

            // Spew tokens in source, for debugging.
            // as TYPE number char
            if (printSource)
            {
                Console.WriteLine("length:" + length);
                for (int i1 = 0; i1 < length; ++i1)
                {
                    // Note that tokenToName will fail unless Context.printTrees
                    // is true.
                    String tokenname = null;
                    if (Token.printNames)
                    {
                        tokenname = Token.name(source.ToCharArray()[i1]);
                    }
                    if (tokenname == null)
                    {
                        tokenname = "---";
                    }
                    String pad = tokenname.Length > 7
                        ? "\t"
                        : "\t\t";
                    Console.WriteLine
                        (tokenname
                         + pad + (int)source.ToCharArray()[i1]
                         + "\t'" + ScriptRuntime.escapeString
                         (source.Substring(i1, i1 + 1))
                         + "'");
                }
                Console.WriteLine();
            }

            int braceNesting = 0;
            bool afterFirstEOL = false;
            int i = 0;
            int topFunctionType;
            if (source.ToCharArray()[i] == Token.SCRIPT)
            {
                ++i;
                topFunctionType = -1;
            }
            else
            {
                topFunctionType = source.ToCharArray()[i + 1];
            }

            if (!toSource)
            {
                // add an initial newline to exactly match js.
                result.Append('\n');
                for (int j = 0; j < indent; j++)
                    result.Append(' ');
            }
            else
            {
                if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION)
                {
                    result.Append('(');
                }
            }

            while (i < length)
            {
                switch ((int)source.ToCharArray()[i])
                {
                    case Token.GET:
                    case Token.SET:
                        result.Append(source.ToCharArray()[i] == Token.GET ? "get " : "set ");
                        ++i;
                        i = printSourceString(source, i + 1, false, result);
                        // Now increment one more to get past the FUNCTION token
                        ++i;
                        break;

                    case Token.NAME:
                    case Token.REGEXP:  // re-wrapped in '/'s in parser...
                        i = printSourceString(source, i + 1, false, result);
                        continue;

                    case Token.STRING:
                        i = printSourceString(source, i + 1, true, result);
                        continue;

                    case Token.NUMBER:
                        i = printSourceNumber(source, i + 1, result);
                        continue;

                    case Token.TRUE:
                        result.Append("true");
                        break;

                    case Token.FALSE:
                        result.Append("false");
                        break;

                    case Token.NULL:
                        result.Append("null");
                        break;

                    case Token.THIS:
                        result.Append("this");
                        break;

                    case Token.FUNCTION:
                        ++i; // skip function type
                        result.Append("function ");
                        break;

                    case FUNCTION_END:
                        // Do nothing
                        break;

                    case Token.COMMA:
                        result.Append(", ");
                        break;

                    case Token.LC:
                        ++braceNesting;
                        if (Token.EOL == getNext(source, length, i))
                            indent += indentGap;
                        result.Append('{');
                        break;

                    case Token.RC:
                        {
                            --braceNesting;
                            /* don't print the closing RC if it closes the
                             * toplevel function and we're called from
                             * decompileFunctionBody.
                             */
                            if (justFunctionBody && braceNesting == 0)
                                break;

                            result.Append('}');
                            switch (getNext(source, length, i))
                            {
                                case Token.EOL:
                                case FUNCTION_END:
                                    indent -= indentGap;
                                    break;
                                case Token.WHILE:
                                case Token.ELSE:
                                    indent -= indentGap;
                                    result.Append(' ');
                                    break;
                            }
                            break;
                        }
                    case Token.LP:
                        result.Append('(');
                        break;

                    case Token.RP:
                        result.Append(')');
                        if (Token.LC == getNext(source, length, i))
                            result.Append(' ');
                        break;

                    case Token.LB:
                        result.Append('[');
                        break;

                    case Token.RB:
                        result.Append(']');
                        break;

                    case Token.EOL:
                        {
                            if (toSource) break;
                            bool newLine = true;
                            if (!afterFirstEOL)
                            {
                                afterFirstEOL = true;
                                if (justFunctionBody)
                                {
                                    /* throw away just added 'function name(...) {'
                                     * and restore the original indent
                                     */
                                    result.Length=0;
                                    indent -= indentGap;
                                    newLine = false;
                                }
                            }
                            if (newLine)
                            {
                                result.Append('\n');
                            }

                            /* add indent if any tokens remain,
                             * less setback if next token is
                             * a label, case or default.
                             */
                            if (i + 1 < length)
                            {
                                int less = 0;
                                int nextToken = source.ToCharArray()[i + 1];
                                if (nextToken == Token.CASE
                                    || nextToken == Token.DEFAULT)
                                {
                                    less = indentGap - caseGap;
                                }
                                else if (nextToken == Token.RC)
                                {
                                    less = indentGap;
                                }

                                /* elaborate check against label... skip past a
                                 * following inlined NAME and look for a COLON.
                                 */
                                else if (nextToken == Token.NAME)
                                {
                                    int afterName = getSourceStringEnd(source, i + 2);
                                    if (source.ToCharArray()[afterName] == Token.COLON)
                                        less = indentGap;
                                }

                                for (; less < indent; less++)
                                    result.Append(' ');
                            }
                            break;
                        }
                    case Token.DOT:
                        result.Append('.');
                        break;

                    case Token.NEW:
                        result.Append("new ");
                        break;

                    case Token.DELPROP:
                        result.Append("delete ");
                        break;

                    case Token.IF:
                        result.Append("if ");
                        break;

                    case Token.ELSE:
                        result.Append("else ");
                        break;

                    case Token.FOR:
                        result.Append("for ");
                        break;

                    case Token.IN:
                        result.Append(" in ");
                        break;

                    case Token.WITH:
                        result.Append("with ");
                        break;

                    case Token.WHILE:
                        result.Append("while ");
                        break;

                    case Token.DO:
                        result.Append("do ");
                        break;

                    case Token.TRY:
                        result.Append("try ");
                        break;

                    case Token.CATCH:
                        result.Append("catch ");
                        break;

                    case Token.FINALLY:
                        result.Append("finally ");
                        break;

                    case Token.THROW:
                        result.Append("throw ");
                        break;

                    case Token.SWITCH:
                        result.Append("switch ");
                        break;

                    case Token.BREAK:
                        result.Append("break");
                        if (Token.NAME == getNext(source, length, i))
                            result.Append(' ');
                        break;

                    case Token.CONTINUE:
                        result.Append("continue");
                        if (Token.NAME == getNext(source, length, i))
                            result.Append(' ');
                        break;

                    case Token.CASE:
                        result.Append("case ");
                        break;

                    case Token.DEFAULT:
                        result.Append("default");
                        break;

                    case Token.RETURN:
                        result.Append("return");
                        if (Token.SEMI != getNext(source, length, i))
                            result.Append(' ');
                        break;

                    case Token.VAR:
                        result.Append("var ");
                        break;

                    case Token.LET:
                        result.Append("let ");
                        break;

                    case Token.SEMI:
                        result.Append(';');
                        if (Token.EOL != getNext(source, length, i))
                        {
                            // separators in FOR
                            result.Append(' ');
                        }
                        break;

                    case Token.ASSIGN:
                        result.Append(" = ");
                        break;

                    case Token.ASSIGN_ADD:
                        result.Append(" += ");
                        break;

                    case Token.ASSIGN_SUB:
                        result.Append(" -= ");
                        break;

                    case Token.ASSIGN_MUL:
                        result.Append(" *= ");
                        break;

                    case Token.ASSIGN_DIV:
                        result.Append(" /= ");
                        break;

                    case Token.ASSIGN_MOD:
                        result.Append(" %= ");
                        break;

                    case Token.ASSIGN_BITOR:
                        result.Append(" |= ");
                        break;

                    case Token.ASSIGN_BITXOR:
                        result.Append(" ^= ");
                        break;

                    case Token.ASSIGN_BITAND:
                        result.Append(" &= ");
                        break;

                    case Token.ASSIGN_LSH:
                        result.Append(" <<= ");
                        break;

                    case Token.ASSIGN_RSH:
                        result.Append(" >>= ");
                        break;

                    case Token.ASSIGN_URSH:
                        result.Append(" >>>= ");
                        break;

                    case Token.HOOK:
                        result.Append(" ? ");
                        break;

                    case Token.OBJECTLIT:
                        // pun OBJECTLIT to mean colon in objlit property
                        // initialization.
                        // This needs to be distinct from COLON in the general case
                        // to distinguish from the colon in a ternary... which needs
                        // different spacing.
                        result.Append(':');
                        break;

                    case Token.COLON:
                        if (Token.EOL == getNext(source, length, i))
                            // it's the end of a label
                            result.Append(':');
                        else
                            // it's the middle part of a ternary
                            result.Append(" : ");
                        break;

                    case Token.OR:
                        result.Append(" || ");
                        break;

                    case Token.AND:
                        result.Append(" && ");
                        break;

                    case Token.BITOR:
                        result.Append(" | ");
                        break;

                    case Token.BITXOR:
                        result.Append(" ^ ");
                        break;

                    case Token.BITAND:
                        result.Append(" & ");
                        break;

                    case Token.SHEQ:
                        result.Append(" === ");
                        break;

                    case Token.SHNE:
                        result.Append(" !== ");
                        break;

                    case Token.EQ:
                        result.Append(" == ");
                        break;

                    case Token.NE:
                        result.Append(" != ");
                        break;

                    case Token.LE:
                        result.Append(" <= ");
                        break;

                    case Token.LT:
                        result.Append(" < ");
                        break;

                    case Token.GE:
                        result.Append(" >= ");
                        break;

                    case Token.GT:
                        result.Append(" > ");
                        break;

                    case Token.INSTANCEOF:
                        result.Append(" instanceof ");
                        break;

                    case Token.LSH:
                        result.Append(" << ");
                        break;

                    case Token.RSH:
                        result.Append(" >> ");
                        break;

                    case Token.URSH:
                        result.Append(" >>> ");
                        break;

                    case Token.TYPEOF:
                        result.Append("typeof ");
                        break;

                    case Token.VOID:
                        result.Append("void ");
                        break;

                    case Token.CONST:
                        result.Append("const ");
                        break;

                    case Token.YIELD:
                        result.Append("yield ");
                        break;

                    case Token.NOT:
                        result.Append('!');
                        break;

                    case Token.BITNOT:
                        result.Append('~');
                        break;

                    case Token.POS:
                        result.Append('+');
                        break;

                    case Token.NEG:
                        result.Append('-');
                        break;

                    case Token.INC:
                        result.Append("++");
                        break;

                    case Token.DEC:
                        result.Append("--");
                        break;

                    case Token.ADD:
                        result.Append(" + ");
                        break;

                    case Token.SUB:
                        result.Append(" - ");
                        break;

                    case Token.MUL:
                        result.Append(" * ");
                        break;

                    case Token.DIV:
                        result.Append(" / ");
                        break;

                    case Token.MOD:
                        result.Append(" % ");
                        break;

                    case Token.COLONCOLON:
                        result.Append("::");
                        break;

                    case Token.DOTDOT:
                        result.Append("..");
                        break;

                    case Token.DOTQUERY:
                        result.Append(".(");
                        break;

                    case Token.XMLATTR:
                        result.Append('@');
                        break;

                    default:
                        // If we don't know how to decompile it, raise an exception.
                        throw new Exception("Token: " +
                                                       Token.name(source.ToCharArray()[i]));
                }
                ++i;
            }

            if (!toSource)
            {
                // add that trailing newline if it's an outermost function.
                if (!justFunctionBody)
                    result.Append('\n');
            }
            else
            {
                if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION)
                {
                    result.Append(')');
                }
            }

            return result.ToString();
        }
        private static int getSourceStringEnd(String source, int offset)
        {
            return printSourceString(source, offset, false, null);
        }
        private static int getNext(String source, int length, int i)
        {
            return (i + 1 < length) ? source.ToCharArray()[i + 1] : Token.EOF;
        }

        private static int printSourceString(String source, int offset,
                                         bool asQuotedString,
                                         StringBuilder sb)
        {
            int length = source.ToCharArray()[offset];
            ++offset;
            if ((0x8000 & length) != 0)
            {
                length = ((0x7FFF & length) << 16) | source.ToCharArray()[offset];
                ++offset;
            }
            if (sb != null)
            {
                String str = source.Substring(offset, length);
                if (!asQuotedString)
                {
                    sb.Append(str);
                }
                else
                {
                    sb.Append('"');
                    sb.Append(ScriptRuntime.escapeString(str));
                    sb.Append('"');
                }
            }
            return offset + length;
        }

        private static int printSourceNumber(String source, int offset,
                                             StringBuilder sb)
        {
            double number = 0.0;
            char type = source.ToCharArray()[offset];
            ++offset;
            if (type == 'S')
            {
                if (sb != null)
                {
                    int ival = source.ToCharArray()[offset];
                    number = ival;
                }
                ++offset;
            }
            else if (type == 'J' || type == 'D')
            {
                if (sb != null)
                {
                    long lbits;
                    lbits = (long)source.ToCharArray()[offset] << 48;
                    lbits |= (long)source.ToCharArray()[offset + 1] << 32;
                    lbits |= (long)source.ToCharArray()[offset + 2] << 16;
                    lbits |= source.ToCharArray()[offset + 3];
                    if (type == 'J')
                    {
                        number = lbits;
                    }
                    else
                    {
                        number =BitConverter.Int64BitsToDouble(lbits);
                    }
                }
                offset += 4;
            }
            else
            {
                // Bad source
                throw new Exception();
            }
            if (sb != null)
            {
                sb.Append(ScriptRuntime.numberToString(number, 10));
            }
            return offset;
        }

        /// <summary>
        /// 源代码缓存
        /// </summary>
        private char[] sourceBuffer = new char[128];
        // Per script/function source buffer top: parent source does not include a
        // nested functions source and uses function index as a reference instead.
        private int sourceTop;
        // whether to do a debug print of the source information, when decompiling.
        private static bool printSource = false;
       
    }
}
